using System;
using System.IO;

namespace Com.FirstSolver.Splash;

public class SM3 : IDisposable
{
	public const int HashSizeInBytes = 32;

	public const int BlockSizeInBytes = 64;

	private static readonly uint[] IV = new uint[8] { 1937774191u, 1226093241u, 388252375u, 3666478592u, 2842636476u, 372324522u, 3817729613u, 2969243214u };

	private static readonly byte[] SM3_PADDING;

	private uint[] V = new uint[8];

	private uint[] W = new uint[68];

	private byte[] M = new byte[4];

	private ulong BytesCount = 0uL;

	private int WOff = 0;

	private int MOff = 0;

	private bool disposed = false;

	public string AlgorithmName => "SM3";

	public int HashSize => 256;

	public SM3()
	{
		Initialize();
	}

	public SM3(SM3 Source)
	{
		BytesCount = Source.BytesCount;
		WOff = Source.WOff;
		if (WOff > 0)
		{
			Array.Copy(Source.W, W, WOff);
		}
		MOff = Source.MOff;
		if (MOff > 0)
		{
			Array.Copy(Source.M, M, MOff);
		}
		Array.Copy(Source.V, V, V.Length);
	}

	protected void Initialize()
	{
		BytesCount = 0uL;
		WOff = 0;
		MOff = 0;
		Array.Copy(IV, V, V.Length);
		Array.Clear(W, 0, W.Length);
	}

	public void Update(byte input)
	{
		M[MOff++] = input;
		if (MOff == 4)
		{
			ProcessWord(M, 0);
			MOff = 0;
		}
		BytesCount++;
	}

	public void BlockUpdate(byte[] input, int offset, int count)
	{
		while (MOff != 0 && count > 0)
		{
			Update(input[offset]);
			offset++;
			count--;
		}
		while (count >= 4)
		{
			ProcessWord(input, offset);
			offset += 4;
			count -= 4;
			BytesCount += 4uL;
		}
		while (count > 0)
		{
			Update(input[offset]);
			offset++;
			count--;
		}
	}

	public void BlockUpdate(byte[] input)
	{
		BlockUpdate(input, 0, input.Length);
	}

	public byte[] DoFinal()
	{
		Finish();
		byte[] output = new byte[32];
		int i = 0;
		uint[] v = V;
		foreach (uint j in v)
		{
			output[i++] = (byte)((j >> 24) & 0xFFu);
			output[i++] = (byte)((j >> 16) & 0xFFu);
			output[i++] = (byte)((j >> 8) & 0xFFu);
			output[i++] = (byte)(j & 0xFFu);
		}
		Initialize();
		return output;
	}

	public int DoFinal(byte[] output, int offset)
	{
		Finish();
		uint[] v = V;
		foreach (uint i in v)
		{
			output[offset++] = (byte)((i >> 24) & 0xFFu);
			output[offset++] = (byte)((i >> 16) & 0xFFu);
			output[offset++] = (byte)((i >> 8) & 0xFFu);
			output[offset++] = (byte)(i & 0xFFu);
		}
		Initialize();
		return 32;
	}

	public static byte[] ComputeHash(Stream input)
	{
		using SM3 sm3 = new SM3();
		byte[] buffer = new byte[4096];
		int ReceivedBytes;
		do
		{
			ReceivedBytes = input.Read(buffer, 0, 4096);
			if (ReceivedBytes > 0)
			{
				sm3.BlockUpdate(buffer, 0, ReceivedBytes);
			}
		}
		while (ReceivedBytes > 0);
		return sm3.DoFinal();
	}

	public static byte[] ComputeHash(byte[] input)
	{
		using SM3 sm3 = new SM3();
		sm3.BlockUpdate(input, 0, input.Length);
		return sm3.DoFinal();
	}

	public static byte[] ComputeHash(byte[] input, int offset, int count)
	{
		using SM3 sm3 = new SM3();
		sm3.BlockUpdate(input, offset, count);
		return sm3.DoFinal();
	}

	public static string ComputeBase64(Stream input)
	{
		return Base64.Encode(ComputeHash(input));
	}

	public static string ComputeBase64(byte[] input)
	{
		return Base64.Encode(ComputeHash(input));
	}

	public static string ComputeBase64(byte[] input, int offset, int count)
	{
		return Base64.Encode(ComputeHash(input, offset, count));
	}

	public static byte[] KDF(byte[] Z, int KLen)
	{
		using SM3 sm3Base = new SM3();
		sm3Base.BlockUpdate(Z, 0, Z.Length);
		return KDF(sm3Base, KLen);
	}

	public static byte[] KDF(SM3 sm3Base, int KLen)
	{
		byte[] K = new byte[KLen];
		int Count = (KLen + 32 - 1) / 32;
		int i = 1;
		int Index = 0;
		while (i <= Count)
		{
			using (SM3 sm3 = new SM3(sm3Base))
			{
				sm3.Update((byte)((uint)(i >> 24) & 0xFFu));
				sm3.Update((byte)((uint)(i >> 16) & 0xFFu));
				sm3.Update((byte)((uint)(i >> 8) & 0xFFu));
				sm3.Update((byte)((uint)i & 0xFFu));
				if (i < Count)
				{
					Array.Copy(sm3.DoFinal(), 0, K, Index, 32);
				}
				else
				{
					Array.Copy(sm3.DoFinal(), 0, K, Index, KLen - Index);
				}
			}
			i++;
			Index += 32;
		}
		return K;
	}

	private void ProcessWord(byte[] input, int offset)
	{
		W[WOff++] = (uint)((input[offset] << 24) | (input[offset + 1] << 16) | (input[offset + 2] << 8) | input[offset + 3]);
		if (WOff == 16)
		{
			ProcessBlock();
		}
	}

	private void ProcessBlock()
	{
		uint[] W1 = new uint[64];
		for (int j = 16; j < 68; j++)
		{
			W[j] = P1(W[j - 16] ^ W[j - 9] ^ Utils.ROTL(W[j - 3], 15)) ^ Utils.ROTL(W[j - 13], 7) ^ W[j - 6];
		}
		for (int l = 0; l < 64; l++)
		{
			W1[l] = W[l] ^ W[l + 4];
		}
		uint A = V[0];
		uint B = V[1];
		uint C = V[2];
		uint D = V[3];
		uint E = V[4];
		uint F = V[5];
		uint G = V[6];
		uint H = V[7];
		for (int k = 0; k < 16; k++)
		{
			uint Q2 = Utils.ROTL(A, 12);
			uint SS2 = Utils.ROTL(Q2 + E + Utils.ROTL(2043430169u, k), 7);
			uint SS4 = SS2 ^ Q2;
			uint TT2 = FF0(A, B, C) + D + SS4 + W1[k];
			uint TT4 = GG0(E, F, G) + H + SS2 + W[k];
			D = C;
			C = Utils.ROTL(B, 9);
			B = A;
			A = TT2;
			H = G;
			G = Utils.ROTL(F, 19);
			F = E;
			E = P0(TT4);
		}
		for (int i = 16; i < 64; i++)
		{
			uint Q = Utils.ROTL(A, 12);
			uint SS1 = Utils.ROTL(Q + E + Utils.ROTL(2055708042u, i), 7);
			uint SS3 = SS1 ^ Q;
			uint TT1 = FF1(A, B, C) + D + SS3 + W1[i];
			uint TT3 = GG1(E, F, G) + H + SS1 + W[i];
			D = C;
			C = Utils.ROTL(B, 9);
			B = A;
			A = TT1;
			H = G;
			G = Utils.ROTL(F, 19);
			F = E;
			E = P0(TT3);
		}
		V[0] ^= A;
		V[1] ^= B;
		V[2] ^= C;
		V[3] ^= D;
		V[4] ^= E;
		V[5] ^= F;
		V[6] ^= G;
		V[7] ^= H;
		WOff = 0;
	}

	private void Finish()
	{
		ulong BitsLength = BytesCount << 3;
		int LeftBytes = (WOff << 2) + MOff;
		int PaddingBytes = ((LeftBytes < 56) ? (56 - LeftBytes) : (120 - LeftBytes));
		BlockUpdate(SM3_PADDING, 0, PaddingBytes);
		byte[] L = BitConverter.GetBytes(BitsLength);
		Array.Reverse(L);
		BlockUpdate(L, 0, 8);
	}

	private uint FF0(uint x, uint y, uint z)
	{
		return x ^ y ^ z;
	}

	private uint FF1(uint x, uint y, uint z)
	{
		return (x & y) | (x & z) | (y & z);
	}

	private uint GG0(uint x, uint y, uint z)
	{
		return x ^ y ^ z;
	}

	private uint GG1(uint x, uint y, uint z)
	{
		return (x & y) | (~x & z);
	}

	private uint P0(uint x)
	{
		return x ^ Utils.ROTL(x, 9) ^ Utils.ROTL(x, 17);
	}

	private uint P1(uint x)
	{
		return x ^ Utils.ROTL(x, 15) ^ Utils.ROTL(x, 23);
	}

	public void Dispose()
	{
		Dispose(disposing: true);
		GC.SuppressFinalize(this);
	}

	protected virtual void Dispose(bool disposing)
	{
		if (!disposed)
		{
			if (disposing)
			{
				Array.Clear(V, 0, V.Length);
			}
			disposed = true;
		}
	}

	static SM3()
	{
		byte[] array = new byte[64];
		array[0] = 128;
		SM3_PADDING = array;
	}
}
